<?php
/**
 * PHP class
 */
class My_Code_Class
{
    /**
     * Class name
     * @var string
     */
    protected $name;

    /**
     * List of class properties
     * @var array
     */
    protected $properties = array();

    /**
     * List of class methods
     * @var array
     */
    protected $methods = array();

    /**
     * Comments
     * @var string
     */
    protected $comments;

    /**
     * @var boolean
     */
    protected $createComments = false;

    /**
     * Name of parent class
     * @var string
     */
    protected $parent;

    /**
     * List of interfazes that class implements
     * @var array
     */
    protected $interfazes = array();

    /**
     * Is this a final class?
     * @var boolean
     */
    protected $final = false;

    /**
     * Is this an abstract class?
     * @var boolean
     */
    protected $abstract = false;


    /**
     * @see Factory
     */
    protected function __construct() {}

    /**
     * @return My_Code_Class
     */
    public static function factory()
    {
        return new self();
    }

    /**
     * Set the class name
     * @param string $name
     * @return My_Code_Class
     */
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }


    /**
     * Set general comments of the class
     * @param string $comments
     * @return My_Code_Class
     */
    public function setClassComments($comments)
    {
        $this->comments = $comments;
        $this->createComments = true;
        return $this;
    }

    /**
     * Is the class final?
     * @param boolean $finalClass
     * @return My_Code_Class
     */
    public function setFinal($finalClass = true)
    {
        $this->final = $finalClass;
        return $this;
    }

    /**
     * Is the class abstract?
     * @param boolean $abstractClass
     * @return My_Code_Class
     */
    public function setAbstract($abstractClass = true)
    {
        $this->abstract = $abstractClass;
        return $this;
    }

    /**
     * Add a property to the class
     * @param string $name
     * @param boolean $createGetter
     * @param boolean $createSetter
     * @param boolean $fluent
     * @return My_Code_Class
     */
    public function addProperty(My_Code_Class_Property $property, $createGetter = false, $createSetter = false, $fluent = false)
    {
        if (is_null($this->name)) throw new Exception ('Class name not defined');

        $this->properties[] = $property;

        if (true === $createGetter)
        {
            $getter = My_Code_Function::factory();
            $getter->setName('get'.ucfirst($property->getName()))
                ->setVisibility('public')
                ->addCodeLine(My_Code_Line::factory('return $this->'.$property->getName()))
                ->setReturnType($property->getType())
                ->createComments();
            $this->addMethod($getter);
        }

        if (true === $createSetter)
        {
            $setter = My_Code_Function::factory();
            $setter->setName('set'.ucfirst($property->getName()))
                ->setVisibility('public')
                ->addParameter(My_Code_Function_Parameter::factory()->setName($property->getName(), $property->getType()))
                ->addCodeLine(My_Code_Line::factory('$this->'.$property->getName().' = $'.$property->getName()));
            if (true == $fluent)
            {
                $setter->addCodeLine(My_Code_Line::factory('return $this'))
                    ->setReturnType($this->name);
            }
            $setter->createComments();
            $this->addMethod($setter);
        }

        return $this;
    }

    /**
     * Get the properties of the class
     * @return array
     */
    public function getProperties($onlyNames = false)
    {
        $properties = $this->properties;
        if (true == $onlyNames)
        {
            $properties = array();
            if (is_array($this->properties) && sizeof($this->properties)>0)
            {
                foreach ($this->properties as $prop)
                {
                    $properties[] = $prop->getNAme();
                }
            }
        }
        return $properties;
    }

    /**
     * Add a method to the class
     * @param My_Code_Function $method
     * @return My_Code_Class
     */
    public function addMethod(My_Code_Function $method)
    {
        $this->methods[] = $method;
        return $this;
    }

    /**
     * Set the parent class
     * @param string $parent
     * @return My_Code_Class
     */
    public function setParent($parent)
    {
        $this->parent = $parent;
        return $this;
    }

    /**
     * Set the implemented interfaces by the class
     * @param array $interfazes
     * @return My_Code_Class 
     */
    public function setInterfazes($interfazes)
    {
        if (!is_array($interfazes)) throw new Exception ('Bad type for $interfaces (only array is valid)');

        $this->interfazes = $interfazes;
        return $this;
    }

    /**
     * The class implements the singleton pattern
     * @param boolean $singletonPattern 
     * @return My_Code_Class
     */
    public function singletonPattern()
    {
        if (is_null($this->name)) throw new Exception ('Class name not defined');

        $staticInstance = $this->addProperty(My_Code_Class_Property::factory()->setName('instance')->staticProperty());

        $protectedConstructor = My_Code_Function::factory()
            ->setName('__construct')
            ->setVisibility('protected')
            ->createComments(true, 'Protected constructor (@see singleton pattern)');
        $this->addMethod($protectedConstructor);

        $singleton = My_Code_Function::factory()
            ->setName('getInstance')
            ->setVisibility('public')
            ->isStatic()
            ->setReturnType($this->name)
            ->addCodeLine(My_Code_Line::factory('if (!(self::$instance instanceof '.$this->name.'))'))
            ->addCodeLine(My_Code_Line::factory('self::$instance = new self()'))
            ->addCodeLine(My_Code_Line::factory('return self::$instance'))
            ->createComments();
        $this->addMethod($singleton);

        return $this;
    }

    /**
     * The class implements the factory pattern
     * @params array de My_Code_Function_Parameter 's
     * @return My_Code_Class
     */
    public function factoryPattern($params = null)
    {

        if (is_null($this->name)) throw new Exception ('Class name not defined');

        if (is_null($params))
        {
            $protectedConstructor = My_Code_Function::factory()
                ->setName('__construct')
                ->setVisibility('protected')
                ->createComments(true, 'Protected constructor (@see factory pattern)');
            $this->addMethod($protectedConstructor);

            $factory = My_Code_Function::factory()
                ->setName('factory')
                ->setVisibility('public')
                ->isStatic()
                ->setReturnType($this->name)
                ->addCodeLine(My_Code_Line::factory('return new self()'))
                ->createComments();
            $this->addMethod($factory);
        }
        else
        {
            $protectedConstructor = My_Code_Function::factory()
                ->setName('__construct')
                ->setVisibility('protected');
            foreach ($params as $param) $protectedConstructor->addParameter($param);
            $protectedConstructor->createComments(true, 'Protected constructor (@see factory pattern)');
            
            foreach ($params as $param)
            {
                if ('array' == $param->getType())
                {
                    $protectedConstructor->addCodeLine(
                        My_Code_Line::factory(sprintf('if (!is_null($%s)) foreach (array_keys($%s) as $name) $this->$name = $%s[$name]',
                                                        $param->getName(), $param->getName(), $param->getName() )));
                }
                else
                {
                    $protectedConstructor->addCodeLine(
                        My_Code_Line::factory(sprintf('$this->$%s = %s', $param->getName(), $param->getName())));
                }
            }

            $this->addMethod($protectedConstructor);

            $factory = My_Code_Function::factory()
                ->setName('factory')
                ->setVisibility('public')
                ->isStatic()
                ->setReturnType($this->name)
                ->addCodeLine(My_Code_Line::factory('return new self()'))
                ->createComments();
            $this->addMethod($factory);
        }
        

        return $this;
    }



    /**
     * @return string
     */
    public function toString()
    {
        if (!is_null($this->parent) && sizeof($this->interfazes) > 0)
        {
            $template = "\nclass %s extends %s implements %s \n{\n\n/*YOUR CODE HERE*/\n\n}";
            $template = sprintf($template, $this->name, $this->parent, implode(',', $this->interfazes));
        }
        elseif (!is_null($this->parent) && 0 == sizeof($this->interfazes))
        {
            $template = "\nclass %s extends %s \n{\n/*YOUR CODE HERE*/}";
            $template = sprintf($template, $this->name, $this->parent);
        }
        elseif (is_null($this->parent) && sizeof($this->interfazes) > 0)
        {
            $template = "\nclass %s implements %s \n{\n/*YOUR CODE HERE*/}";
            $template = sprintf($template, $this->name, implode(',', $this->interfazes));
        }
        else
        {
             $template = "\nclass %s \n{\n/*YOUR CODE HERE*/}";
             $template = sprintf($template, $this->name);
        }

        $template = str_replace('/*YOUR CODE HERE*/', '%s', $template);

        $code = '';
        $auxProperties = array();
        foreach ($this->properties as $property)
        {
            $auxProperties[] = $property->toString();
        }

        $auxMethods = array();
        foreach ($this->methods as $method)
        {
            $auxMethods[] = $method->toString();
        }

        $code .= implode("\n", $auxProperties);
        $code .= "\n \n";
        $code .= implode("\n\n", $auxMethods);

        if (true == $this->abstract)
            $template = 'abstract '.$template;

        if (true == $this->final)
            $template = 'final '.$template;

        return sprintf($template, $code);
    }

}